/* capture.c
 * Routines for packet capture windows
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@zing.org>
 * Copyright 1998 Gerald Combs
 *
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#include "config.h"

#include <gtk/gtk.h>
#include <pcap.h>

#include <string.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>

#ifdef HAVE_SYS_SOCKIO_H
  #include <sys/sockio.h>
#endif

#include "packet.h"
#include "file.h"
#include "capture.h"
#include "etypes.h"

extern capture_file cf;

GList *
get_interface_list() {
  GList  *il = NULL;
  struct  ifreq *ifr, *last;
  struct  ifconf ifc;
  int     sock = socket(AF_INET, SOCK_DGRAM, 0);

  if (sock < 0)
  {
    perror("socket");
    return NULL;
  }

  /* Since we have to grab the interface list all at once, we'll make
     plenty of room */
  ifc.ifc_len = 1024 * sizeof(struct ifreq);
  ifc.ifc_buf = malloc(ifc.ifc_len);

  if (ioctl(sock, SIOCGIFCONF, &ifc) < 0)
  {
    perror("ioctl");
    return NULL;
  }

  ifr  = (struct ifreq *) ifc.ifc_req;
  last = (struct ifreq *) ((char *) ifr + ifc.ifc_len);
  while (ifr < last)
  {
    /*
     * What we want:
     * - Interfaces that are up, and not loopback
     * - IP interfaces
     * - Anything that doesn't begin with "lo" (loopback again) or "dummy"
     * - Anything that doesn't include a ":" (Solaris virtuals)
     */
    if (! (ifr->ifr_flags & (IFF_UP | IFF_LOOPBACK)) &&
        (ifr->ifr_addr.sa_family == AF_INET) &&
        strncmp(ifr->ifr_name, "lo", 2) &&
        strncmp(ifr->ifr_name, "dummy", 5) &&
        ! strchr(ifr->ifr_name, ':')) {
      il = g_list_append(il, g_strdup(ifr->ifr_name));
    }
/* AIX can really be a pain at times */
#ifdef _AIX41
    ifr = (struct ifreq *) ((char *) ifr + ifr->ifr_addr.sa_len + IFNAMSIZ);
#else
    ifr = (struct ifreq *) ((char *) ifr + sizeof(struct ifreq));
#endif
  }

  free(ifc.ifc_buf);
  return il;
}

void
capture_prep_cb(GtkWidget *w, gpointer d) {
  GtkWidget *cap_open_w, *if_cb, *if_lb, *file_te, *file_bt, *count_lb,
            *count_cb, *main_vb, *top_hb, *middle_hb, *bottom_hb,
            *bbox, *ok_bt, *cancel_bt, *capfile_ck;
  GList     *if_list, *count_list = NULL;
  co_data   *ok_cb_data = (co_data *) g_malloc(sizeof(co_data));
  gchar     *count_item = "0 (Infinite)";

  cap_open_w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(cap_open_w), "Ethereal: Capture Preferences");
  
  /* Container for each row of widgets */
  main_vb = gtk_vbox_new(FALSE, 3);
  gtk_container_border_width(GTK_CONTAINER(main_vb), 5);
  gtk_container_add(GTK_CONTAINER(cap_open_w), main_vb);
  gtk_widget_show(main_vb);
  
  /* Top row: Interface and count selections */
  top_hb = gtk_hbox_new(FALSE, 1);
  gtk_container_add(GTK_CONTAINER(main_vb), top_hb);
  gtk_widget_show(top_hb);
  
  if_lb = gtk_label_new("Interface:");
  gtk_box_pack_start(GTK_BOX(top_hb), if_lb, FALSE, FALSE, 3);
  gtk_widget_show(if_lb);
  
  if_list = get_interface_list();
  if_cb = gtk_combo_new();
  gtk_combo_set_popdown_strings(GTK_COMBO(if_cb), if_list);
  if (if_list)
    gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(if_cb)->entry), if_list->data);
  gtk_box_pack_start(GTK_BOX(top_hb), if_cb, FALSE, FALSE, 3);
  gtk_widget_show(if_cb);
  while (if_list) {
    g_free(if_list->data);
    if_list = g_list_remove_link(if_list, if_list);
  }

  count_list = g_list_append(count_list, count_item);
  count_cb = gtk_combo_new();
  gtk_combo_set_popdown_strings(GTK_COMBO(count_cb), count_list);
  gtk_box_pack_end(GTK_BOX(top_hb), count_cb, FALSE, FALSE, 3);
  gtk_widget_show(count_cb);
  g_list_remove_link(count_list, count_list);

  count_lb = gtk_label_new("Count:");
  gtk_box_pack_end(GTK_BOX(top_hb), count_lb, FALSE, FALSE, 3);
  gtk_widget_show(count_lb);
  
  /* Middle row: File: button and text entry */
  middle_hb = gtk_hbox_new(FALSE, 1);
  gtk_container_add(GTK_CONTAINER(main_vb), middle_hb);
  gtk_widget_show(middle_hb);
  
  file_bt = gtk_button_new_with_label("File:");
  gtk_box_pack_start(GTK_BOX(middle_hb), file_bt, FALSE, FALSE, 3);
  gtk_widget_show(file_bt);
  
  file_te = gtk_entry_new();
  gtk_box_pack_start(GTK_BOX(middle_hb), file_te, TRUE, TRUE, 3);
  gtk_widget_show(file_te);

  gtk_signal_connect_object(GTK_OBJECT(file_bt), "clicked",
    GTK_SIGNAL_FUNC(capture_prep_file_cb), GTK_OBJECT(file_te));

  /* Bottom row: Capture file checkbox */
  bottom_hb = gtk_hbox_new(FALSE, 1);
  gtk_container_add(GTK_CONTAINER(main_vb), bottom_hb);
  gtk_widget_show(bottom_hb);
  
  capfile_ck = gtk_check_button_new_with_label("Open file after capture");
  gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(capfile_ck), TRUE);
  gtk_box_pack_start(GTK_BOX(bottom_hb), capfile_ck, FALSE, FALSE, 3);
  gtk_widget_show(capfile_ck);
  
  /* Button row: OK and cancel buttons */
  bbox = gtk_hbutton_box_new();
  gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_END);
  gtk_container_add(GTK_CONTAINER(main_vb), bbox);
  gtk_widget_show(bbox);
  
  ok_bt = gtk_button_new_with_label ("OK");
  gtk_signal_connect(GTK_OBJECT(ok_bt), "clicked",
    GTK_SIGNAL_FUNC(capture_prep_ok_cb), (gpointer) ok_cb_data);
  gtk_container_add(GTK_CONTAINER(bbox), ok_bt);
/*  GTK_WIDGET_SET_FLAGS(ok_bt, GTK_CAN_DEFAULT);
  gtk_widget_grab_default(ok_bt);  */
  gtk_widget_show(ok_bt);

  cancel_bt = gtk_button_new_with_label ("Cancel");
  gtk_signal_connect_object(GTK_OBJECT(cancel_bt), "clicked",
    GTK_SIGNAL_FUNC(capture_prep_close_cb), GTK_OBJECT(cap_open_w));
  gtk_container_add(GTK_CONTAINER(bbox), cancel_bt);
  gtk_widget_show(cancel_bt);

  /* Place the important widgets in the callback struct */
  ok_cb_data->win   = cap_open_w;
  ok_cb_data->combo = if_cb;
  ok_cb_data->file  = file_te;
  ok_cb_data->count = count_cb;
  ok_cb_data->open  = capfile_ck;
  
  gtk_widget_show(cap_open_w);
}

void
capture_prep_file_cb(GtkWidget *w, gpointer te) {
  GtkWidget *fs, **w_list;

  w_list = g_malloc(2 * sizeof(GtkWidget *));
  
  fs = gtk_file_selection_new ("Ethereal: Open Save File");
  w_list[0] = fs;
  w_list[1] = (GtkWidget *) te;
  
  gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION(fs)->ok_button),
    "clicked", (GtkSignalFunc) cap_prep_fs_ok_cb, w_list);

  /* Connect the cancel_button to destroy the widget */
  gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION(fs)->cancel_button),
    "clicked", (GtkSignalFunc) cap_prep_fs_cancel_cb, w_list);
  
  gtk_widget_show(fs);
}

void
cap_prep_fs_ok_cb(GtkWidget *w, gpointer data) {
  GtkWidget **w_list = (GtkWidget **) data;

  gtk_entry_set_text(GTK_ENTRY(w_list[1]),
    gtk_file_selection_get_filename (GTK_FILE_SELECTION(w_list[0])));
  cap_prep_fs_cancel_cb(w, data);
}

void
cap_prep_fs_cancel_cb(GtkWidget *w, gpointer data) {
  GtkWidget **w_list = (GtkWidget **) data;

  gtk_widget_destroy(w_list[0]);
  g_free(data);
}  

void
capture_prep_ok_cb(GtkWidget *w, gpointer data) {
  co_data *cd = (co_data *) data;
  gchar   *interface, *file;
  gint     count, open;

  interface =
    g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(cd->combo)->entry)));
  file = g_strdup(gtk_entry_get_text(GTK_ENTRY(cd->file)));
  count =
    atoi(g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(cd->count)->entry))));
  open = GTK_TOGGLE_BUTTON(cd->open)->active;

  gtk_widget_destroy(GTK_WIDGET(cd->win));
  
  capture(interface, count, file, open, 68);
}

void
capture_prep_close_cb(GtkWidget *w, gpointer win) {

  gtk_grab_remove(GTK_WIDGET(win));
  gtk_widget_destroy(GTK_WIDGET(win));
}

void
capture(gchar *interface, gint count, gchar *file, gint open, gint caplen) {
  GtkWidget *cap_w, *main_vb, *count_lb, *tcp_lb, *udp_lb, *other_lb,
            *stop_bt;
  pcap_t    *pch;
  gchar      err_str[PCAP_ERRBUF_SIZE], label_str[32];
  loop_data  ld;
  
  ld.go    = TRUE;
  ld.count = 0;
  ld.max   = count;
  ld.tcp   = 0;
  ld.udp   = 0;
  ld.other = 0;
  ld.pdh   = NULL;
  
  cap_w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(cap_w), "Ethereal: Capture / Playback");

  /* Container for capture display widgets */
  main_vb = gtk_vbox_new(FALSE, 1);
  gtk_container_border_width(GTK_CONTAINER(main_vb), 5);
  gtk_container_add(GTK_CONTAINER(cap_w), main_vb);
  gtk_widget_show(main_vb);

  count_lb = gtk_label_new("Count: 0");
  gtk_box_pack_start(GTK_BOX(main_vb), count_lb, FALSE, FALSE, 3);
  gtk_widget_show(count_lb);

  tcp_lb = gtk_label_new("TCP: 0 (0.0%)");
  gtk_box_pack_start(GTK_BOX(main_vb), tcp_lb, FALSE, FALSE, 3);
  gtk_widget_show(tcp_lb);

  udp_lb = gtk_label_new("UDP: 0 (0.0%)");
  gtk_box_pack_start(GTK_BOX(main_vb), udp_lb, FALSE, FALSE, 3);
  gtk_widget_show(udp_lb);

  other_lb = gtk_label_new("Other: 0 (0.0%)");
  gtk_box_pack_start(GTK_BOX(main_vb), other_lb, FALSE, FALSE, 3);
  gtk_widget_show(other_lb);

  stop_bt = gtk_button_new_with_label ("Stop");
  gtk_signal_connect(GTK_OBJECT(stop_bt), "clicked",
    GTK_SIGNAL_FUNC(capture_stop_cb), (gpointer) &ld);
  gtk_box_pack_end(GTK_BOX(main_vb), stop_bt, FALSE, FALSE, 3);
  GTK_WIDGET_SET_FLAGS(stop_bt, GTK_CAN_DEFAULT);
  gtk_widget_grab_default(stop_bt);
  GTK_WIDGET_SET_FLAGS(stop_bt, GTK_CAN_DEFAULT);
  gtk_widget_grab_default(stop_bt);
  gtk_widget_show(stop_bt);
  
  gtk_widget_show(cap_w);
  gtk_grab_add(cap_w);

  pch = pcap_open_live(interface, caplen, 1, 250, err_str);

  if (pch) {
    if (file[0]) ld.pdh = pcap_dump_open(pch, file);

    while (ld.go) {
      while (gtk_events_pending()) gtk_main_iteration();
      pcap_dispatch(pch, 1, capture_pcap_cb, (u_char *) &ld);
      sprintf(label_str, "Count: %d", ld.count);
      gtk_label_set(GTK_LABEL(count_lb), label_str);
      sprintf(label_str, "TCP: %d (%.1f%%)", ld.tcp, pct(ld.tcp, ld.count));
      gtk_label_set(GTK_LABEL(tcp_lb), label_str);
      sprintf(label_str, "UDP: %d (%.1f%%)", ld.udp, pct(ld.udp, ld.count));
      gtk_label_set(GTK_LABEL(udp_lb), label_str);
      sprintf(label_str, "Other: %d (%.1f%%)", ld.other,
        pct(ld.other, ld.count));
      gtk_label_set(GTK_LABEL(other_lb), label_str);
    }
    
    if (ld.pdh) pcap_dump_close(ld.pdh);
    pcap_close(pch);
  } else {
    while (gtk_events_pending()) gtk_main_iteration();
    g_warning("ethereal:capture:pcap_open_live:%s", err_str);
  }

  g_free(interface);
  gtk_grab_remove(GTK_WIDGET(cap_w));
  gtk_widget_destroy(GTK_WIDGET(cap_w));
  
  if (file[0] && open) read_cap_file(file, &cf);
}

float
pct(gint num, gint denom) {
  if (denom) {
    return (float) num * 100.0 / (float) denom;
  } else {
    return 0.0;
  }
}

void
capture_stop_cb(GtkWidget *w, gpointer data) {
  loop_data *ld = (loop_data *) data;
  
  ld->go = FALSE;
}

void
capture_pcap_cb(u_char *user, const struct pcap_pkthdr *phdr,
  const u_char *pd) {
  
  guint16 etype;
  guint8  iptype = 0;
  gint    offset = 14;
  
  loop_data *ld = (loop_data *) user;
  
  if ((++ld->count > ld->max) && (ld->max > 0)) ld->go = FALSE;
  /* Currently, pcap_dumper_t is a FILE *.  Let's hope that doesn't change. */
  if (ld->pdh) pcap_dump((u_char *) ld->pdh, phdr, pd);
  
  etype = etype = (pd[12] << 8) | pd[13];
  if (etype <= IEEE_802_3_MAX_LEN) {
    etype = (pd[20] << 8) | pd[21];
    offset = 22;
  }
  
  if (etype == ETHERTYPE_IP) {
    iptype = pd[offset + 9];
    switch (iptype) {
      case 6:
        ld->tcp++;
        break;
      case 17:
        ld->udp++;
        break;
      default:
        ld->other++;
    }
  } else {
    ld->other++;
  }
}
